// Copyright (C) 2024 Kumo inc.
// Author: Jeff.li lijippy@163.com
// All rights reserved.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published
// by the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <https://www.gnu.org/licenses/>.
//

#pragma once

#include <memory>
#include <stdexcept>

namespace deneb::internal {

    // A roll-your-own replacement of `std::optional`.
    //
    // This class is only to be used if `std::optional` is unavailable. It
    // implements an optional type simply on top of a `unique_ptr`. It is
    // API-compatible with `std::optional`, as required for our purposes.
    template<typename T>
    class Optional {
    public:
        /// Constructor.
        Optional() = default;

        /// Copy constructor.
        ///
        /// \param other The other optional object to copy from.
        Optional(const Optional &other) {
            if (other) emplace(*other);
        }

        /// Generalized copy constructor.
        ///
        /// \param other The other optional object to copy from.
        template<typename U,
                typename = std::enable_if_t<std::is_convertible<T, U>::value>>
        Optional(const Optional<U> &other) {
            if (other) emplace(*other);
        }

        /// Move constructor.
        ///
        /// \param other The other optional object to move into this one.
        Optional(Optional &&other) noexcept {
            swap(other);
        }

        /// Generalized move constructor.
        ///
        /// \param other The other optional object to move into this one.
        template<typename U,
                typename = std::enable_if_t<std::is_convertible<T, U>::value>>
        Optional(Optional<U> &&other) noexcept {
            if (other) {
                _value = std::make_unique<T>(std::move(*other));
            }
        }

        /// Assignment operator.
        ///
        /// \param other The other object to assign from.
        /// \returns The resulting optional instance.
        Optional &operator=(Optional other) noexcept {
            swap(other);
            return *this;
        }

        /// Swaps the contents of this optional with another one.
        ///
        /// \param other The other optional to swap with.
        void swap(Optional &other) {
            _value.swap(other._value);
        }

        /// Swaps the contents of two optionals.
        ///
        /// \param first The first optional to swap.
        /// \param second The second optional to swap.
        friend void swap(Optional &first, Optional &second) /* NOLINT */ {
            first.swap(second);
        }

        /// \returns True if the `Optional` has a value, else false.
        bool has_value() const noexcept {
            return static_cast<bool>(_value);
        }

        /// \copydoc has_value()
        explicit operator bool() const noexcept {
            return has_value();
        }

        /// \returns A pointer to the current value. Behavior is undefined if the
        /// optional has no value.
        T *operator->() {
            return _value.get();
        }

        /// \returns A const pointer to the current value. Behavior is undefined if
        /// the `Optional` has no value.
        const T *operator->() const {
            return _value.get();
        }

        /// \returns A const reference to the current value. Behavior is undefined if
        /// the `Optional` has no value.
        const T &operator*() const {
            return *_value;
        }

        /// \returns A reference to the current value. Behavior is undefined if
        /// the `Optional` has no value.
        T &operator*() {
            return *_value;
        }

        /// \returns A reference to the current value.
        /// \throws std::runtime_error If the `Optional` currently has no value.
        T &value() {
            if (!has_value()) {
                // Actually std::bad_optional_access
                throw std::runtime_error("optional has no value");
            }

            return *_value;
        }

        /// \returns A const reference to the current value.
        /// \throws std::runtime_error If the `Optional` currently has no value.
        const T &value() const {
            if (!has_value()) {
                // Actually std::bad_optional_access
                throw std::runtime_error("optional has no value");
            }

            return *_value;
        }

        /// \returns The current value, or the given argument if there is no value.
        /// \param default_value The value to return if this `Optional` currently has
        ///                      no value.
        template<class U>
        T value_or(U &&default_value) const {
            return *this ? **this : static_cast<T>(std::forward<U>(default_value));
        }

        /// Resets the `Optional` to have no value.
        void reset() noexcept {
            _value.reset();
        }

        /// Constructs the `Optional`'s value with the given arguments.
        ///
        /// \param args Arguments to perfeclty forward to the value's constructor.
        template<typename... Args>
        void emplace(Args &&... args) {
            _value = std::make_unique<T>(std::forward<Args>(args)...);
        }

    private:
        template<typename>
        friend
        class Optional;

        /// The value, as we implement it.
        std::unique_ptr<T> _value;
    };
}  // namespace deneb::internal
